﻿#region Namespace imports

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
#if USENOVELL
using Novell.Directory.Ldap;
#else
using System.DirectoryServices;
#endif
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

#endregion

namespace Novell.DirectoryServices.Linq
{
    /// <summary>
    /// Provides context-based provider support.
    /// </summary>
    public class DirectoryContext : IDisposable
    {
        #region Constructors and Fieldds

        private LdapConnection _connection;
        private string _searchRoot;


        /// <summary>
        /// Creates a new data source instance for the given directory search root and with a given search scope.
        /// </summary>
        /// <param name="searchRoot">Root location in the directory to start all searches from.</param>
        public DirectoryContext(LdapConnection connection, string searchRoot)
        {
            _connection = connection;
            _searchRoot = searchRoot;
            InitializeSources();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Used to configure a logger to print diagnostic information about the query performed.
        /// </summary>
        public TextWriter Log { get; set; }

        /// <summary>
        /// Searcher object.
        /// </summary>
        public LdapConnection Connection { get { return _connection; } }

        public string SearchRoot
        {
            get { return _searchRoot; }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Gets the DirectorySource for the given entity type.
        /// </summary>
        /// <typeparam name="T">Entity type.</typeparam>
        /// <param name="scope">Search scope.</param>
        /// <returns>DirectorySource for the given entity type.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public DirectorySource<T> GetSource<T>(SearchScope scope)
        {

            return new DirectorySource<T>(this, new SearchOptions(this.SearchRoot,scope));
        }

        /// <summary>
        /// Gets an UpdatableDirectorySource for the given entity type.
        /// </summary>
        /// <typeparam name="T">Entity type.</typeparam>
        /// <param name="scope">Search scope.</param>
        /// <returns>DirectorySource for the given entity type.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public UpdatableDirectorySource<T> GetUpdatableSource<T>(SearchScope scope) where T : DirectoryEntity
        {
            return new UpdatableDirectorySource<T>(this, new SearchOptions(this.SearchRoot, scope));
        }
        /// <summary>
        /// Updates all data sources captured by the context object recursively (including nested contexts).
        /// </summary>
        public void SubmitChanges()
        {
            foreach (PropertyInfo property in this.GetType().GetProperties())
            {
                if (typeof(IUpdatableDirectorySource).IsAssignableFrom(property.PropertyType))
                {
                    ((IUpdatableDirectorySource)property.GetValue(this, null)).SubmitChanges();
                }
                else 
                    if (typeof(DirectoryContext).IsAssignableFrom(property.PropertyType))
                {
                    ((DirectoryContext)property.GetValue(this, null)).SubmitChanges();
                }
            }
        }

        #endregion

        #region Helper methods

        /// <summary>
        /// Helper method to initialize data sources.
        /// </summary>
        internal void InitializeSources()
        {

            MethodInfo getSource = this.GetType().GetMethod("GetSource");
            MethodInfo getUpdatableSource = this.GetType().GetMethod("GetUpdatableSource");

            foreach (PropertyInfo property in this.GetType().GetProperties())
            {
                if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(UpdatableDirectorySource<>))
                {
                    var searchOptions = property.GetCustomAttributes(typeof(DirectorySearchOptionsAttribute), true).Cast<DirectorySearchOptionsAttribute>().FirstOrDefault();
                    SearchScope scope = searchOptions != null ? searchOptions.Scope : SearchScope.Base;

                    Type entity = property.PropertyType.GetGenericArguments()[0];
                    property.SetValue(this, getUpdatableSource.MakeGenericMethod(entity).Invoke(this, new object[] { scope }), null);
                }
                else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(DirectorySource<>))
                {
                    var searchOptions = property.GetCustomAttributes(typeof(DirectorySearchOptionsAttribute), true).Cast<DirectorySearchOptionsAttribute>().FirstOrDefault();
                    SearchScope scope = searchOptions != null ? searchOptions.Scope : SearchScope.Base;
                    
                    Type entity = property.PropertyType.GetGenericArguments()[0];
                    property.SetValue(this, getSource.MakeGenericMethod(entity).Invoke(this, new object[] { scope }), null);
                }
                //Support Child Directory Contexts.
                else if (typeof(DirectoryContext).IsAssignableFrom(property.PropertyType))
                {
                    var searchPath = property.GetCustomAttributes(typeof(DirectorySearchPathAttribute), true).Cast<DirectorySearchPathAttribute>().FirstOrDefault();

                    string searchRoot = null;
                    if (searchPath != null && !string.IsNullOrEmpty(searchPath.Path))
                    {
                        searchRoot = string.Format(CultureInfo.InvariantCulture,"{0},{1}", searchPath, _searchRoot);
                    }

                    Type subContext = property.PropertyType;
                    ConstructorInfo ctor = subContext.GetConstructor(new Type[] { typeof(LdapConnection), typeof(string) });
                    if (ctor == null)
                        throw new InvalidOperationException("Nested context " + property.Name + " does not have a suitable constructor.");

                    property.SetValue(this, ctor.Invoke(new object[] { this._connection,searchRoot??_searchRoot }), null);

                }
            }
        }

        #endregion

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                //Do we need this?
                if (_connection != null)
                {
                    //Used to disconnect the connection
                    _connection.Disconnect();
                    _connection = null;
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
